#include "TFrame.h"
#include <signal.h>

/*********** function implementation *************/
void WorkQueue::setNumThreads(int number) {// set the number of threads to the number //
  num_threads = number;
}

void* run_thread (void* x) {
    //    WorkQueue* Q = (WorkQueue*)x;
    struct thread_args * myargs = (struct thread_args*)x;
    WorkQueue * Q = myargs->_Q;
    int i = myargs->id;
    while (true) {
        if (!myargs->_visited->getBit(i)) {
            std::cout << "Killing thread " << i << std::endl;
            pthread_exit(NULL);  
            continue;
        }
        /*
        bool qEmpty;
        pthread_mutex_lock(&Q->qmutex); // acquire the mutex on the queue //
        qEmpty = Q->workq.empty();
        pthread_mutex_unlock(&Q->qmutex);
        */
        pthread_mutex_lock(&Q->qmutex);
        while (Q->workq.empty()) {// queue is empty wait for it to be filled 
            // wait on conditional variables //
            pthread_mutex_lock(&Q->cmutex);
            Q->count++;
            if(Q->count == Q->num_threads) {
                pthread_cond_signal(&Q->done);
            }
            pthread_mutex_unlock(&Q->qmutex);
            pthread_cond_wait(&Q->empty_cond, &Q->cmutex);
            Q->count--;
            pthread_mutex_unlock(&Q->cmutex);
            pthread_mutex_lock(&Q->qmutex);
        }
        Task * myTask = NULL;
        //pthread_mutex_lock(&Q->qmutex);
        myTask = Q->workq.front();
        if (myTask != NULL) 
            Q->workq.pop();
        else {
            pthread_mutex_unlock(&Q->qmutex); 
            continue;
        }
        pthread_mutex_unlock(&Q->qmutex);
        myTask->run();

        if((myTask->doneState)==Done) {
            //myTask->doneWithTask();        
            pthread_cond_broadcast(&myTask->tdone);
            //delete myTask->doneState;
            //delete myTask;
            myTask->done();
            myTask = NULL;
        } else if ((myTask->doneState)==Working){
            Q->addTask(myTask);
        }

    }
    pthread_exit(NULL);  
}

// monitor function
void * run_monitor(void * inarg) {
#if 0
    glibtop_init();
    glibtop_cpu cpu;
    unsigned long prev_value = 0;
    unsigned long curr_value;
    float percentage;
    struct thread_args * myargs = (struct thread_args *)inarg;
    dbitvec * enabled = myargs->_visited;
    WorkQueue * Q = myargs->_Q;
    int num_threads = enabled->length();
    while (true) {
        glibtop_get_cpu(&cpu);
        curr_value = (unsigned long)cpu.user / (unsigned long)cpu.frequency;
        if (prev_value = 0)
            prev_value = curr_value;
        else {
            percentage = ((float)(curr_value - prev_value) / (float)(curr_value)) * 10.f;
            if (percentage > 50) {
                Q->pool.pop_back();
                enabled->setBit(Q->pool.size()-1, false); // set bti for thread to false to stop it from working
            } else {
                //enabled->resize(num_threads+1);
                Q->pool.push_back(new pthread_t);
                int i = Q->pool.size();
                struct thread_args * tosend = new struct thread_args();
                tosend->_Q = Q;
                tosend->_visited = enabled;
                tosend->id = i-1;
                pthread_create(Q->pool[i-1], NULL, run_thread, (void*)tosend);
                enabled->resize(i);
                enabled->setBit(i-1, true);
            }
            prev_value = curr_value;
        }
        sleep(2);
    }
#endif 
}

WorkQueue::WorkQueue() {
    count = 0;
    pthread_mutex_init(&qmutex, NULL);
    pthread_mutex_init(&cmutex, NULL);
    pthread_cond_init (&empty_cond, NULL);
    pthread_cond_init (&done, NULL);
    struct thread_args * args = new struct thread_args();
    args->_Q = this;
    args->_visited = &enabled;
    args->id = -1;
#if 0
    pthread_create(&monitor, NULL, run_monitor, (void*)(args)); // create monitor thread
#endif
}

int WorkQueue::initThreads() {
    if (!num_threads) {
        fprintf(stderr, "You must initialize the number of threads!\n");
        exit(EXIT_FAILURE);
    }
    enabled.resize(num_threads);
    for (int i=0; i < num_threads; i++) {
        pool.push_back(new pthread_t());
        struct thread_args * args = new struct thread_args();
        args->_Q = this;
        args->_visited = &enabled;
        args->id = i;
        pthread_create(pool[i], NULL, run_thread, (void*)args);
        enabled.setBit(i, true);
    } 
    return 0;
}

int WorkQueue::addTask(Task* t) {
    t->_Done = false;
    pthread_mutex_lock(&qmutex);
    workq.push(t);
    pthread_mutex_unlock(&qmutex);

    pthread_mutex_lock(&cmutex);
    if (count > 0) 
        pthread_cond_broadcast(&empty_cond);
    pthread_mutex_unlock(&cmutex);
    return 0;
}

int WorkQueue::wait_for_threads() {
    if (!num_threads)
        return -1;
    pthread_mutex_lock(&cmutex);
    pthread_mutex_lock(&qmutex);
    if (count == num_threads && workq.empty()) {
        pthread_mutex_unlock(&qmutex);
        pthread_mutex_unlock(&cmutex);
        printf("All threads are done!\n");
        //delete[] pool; pool = NULL;
        return 0;
    } else {
        pthread_mutex_unlock(&qmutex);
        pthread_cond_wait(&done, &cmutex);
        pthread_mutex_unlock(&cmutex);
        printf("All threads are done!\n");
        //delete[] pool; pool = NULL;
        return 0;
    }
}

void wait_for_task(Task* t) {// wait for a specific task to be done //
    pthread_mutex_lock(&t->dmutex);
    if (!t->_Done) {// not done yet //
        pthread_cond_wait(&t->tdone, &t->dmutex);// wait for the signal to be done //
        pthread_mutex_unlock(&t->dmutex);
        return;
    }else {
        pthread_mutex_unlock(&t->dmutex); //simply leave //
        return;
    }
}
/******* End of function implementation ***********/
